<?php

namespace app\index\service\payments;

use app\common\model\Account;
use app\common\model\AccountBill;
use app\common\model\AccountSystem;
use app\common\model\AccountUser;
use app\common\model\Trade;
use app\common\model\Transfer;
use app\common\service\AccountBillService;
use ShidianUtil\Concurrent\Synchronizer;
use ShidianUtil\Utils\StringUtil;
use think\exception\ValidateException;
use think\facade\Log;

/**
 * 本地账户
 * Class AbstractAccountPaymentService
 * @package app\index\service\payments
 */
abstract class AbstractAccountPaymentService implements PaymentInterface, TransferInterface, ProxyPaymentInterface
{
    // 当前支付方式
    const PAYMETHOD = PAYMENT_ACCOUNT;

    // ****************** 错误码 ***************************

    // 正常
    const OK = 0;

    // 用户账户未找到
    const ERROR_CODE_MEMBER_ACCOUNT_NOT_FOUND = 400;
    // 用户账户余额不足
    const ERROR_CODE_MEMBER_ACCOUNT_BALANCE_NOT_ENOUGH = 401;

    // 平台账户未找到
    const ERROR_CODE_SYSTEM_ACCOUNT_NOT_FOUND = 500;


    private Synchronizer $synchronizer;

    public function __construct(Synchronizer $synchronizer)
    {
        $this->synchronizer = $synchronizer;
    }

    // **************** 支付 ****************/

    /**
     * 支付失败处理
     * @return mixed
     */
    abstract protected function payFailedHandle($direction, $memberId, $payMethod, $money, $memberBeforeMoney, $systemBeforeMoney, $errorCode);

    /**
     * 支付预下单
     * @param Trade $trade
     * @param bool $arrears
     * @return array|string[]
     */
    public function pay(Trade $trade, bool $arrears = false): array
    {
        $payMethod = static::PAYMETHOD;
        $money = $trade->payMoney;
        $memberType = 'member';
        $memberId = $trade->memberId;
        $tradeId = $trade->id;
        $tradeType = $trade->tradeType;
        $describe = $trade->describe;

        // 余额支付
        $direction = true;

        $success = $this->accountHandle($direction, $memberType, $memberId, $payMethod, $money, $arrears, $memberBeforeMoney, $systemBeforeMoney, $errorCode);

        if (!$success) {
            $this->payFailedHandle($direction, $memberId, $payMethod, $money, $memberBeforeMoney, $systemBeforeMoney, $errorCode);
            return [];
        }

        // 插入账单
        $bill = $this->getAccountBillService()->insertPay($memberType, $memberId, $memberBeforeMoney, $systemBeforeMoney, $payMethod, $money,
            $tradeType, $describe, 0, 0, $tradeId, null);

        // 交易账单ID作为三方支付单号来保存
        if ($trade->payStatus != Trade::UNPAID) {
            if ($trade->payStatus == Trade::PAID) {
                throw new ValidateException('该订单已支付');
            }
            if ($trade->payStatus == Trade::CANCELLED) {
                throw new ValidateException('该订单已取消');
            }
            throw new ValidateException('该订单已支付');
        }

        $trade->payMemberType = $memberType;
        $trade->payMemberId = $memberId;

        $trade->tradeNid = $bill->id;
        $trade->payStatus = Trade::PAID;
        $trade->payTime = time();
        $trade->save();

        // 触发支付成功事件
        event('PaySuccess', $trade);

        return [
            'needPay' => '102',
        ];
    }

    /**
     * 支付取消预下单
     * @param Trade $trade
     * @return void
     */
    public function cancelPay(Trade $trade): void
    {
        // 本地账户什么都不用做
        return;
    }

    /**
     * 主动查询支付结果
     * @param Trade $trade
     * @return void
     */
    public function query(Trade $trade): void
    {
        // 本地账户什么都不用做
        return;
    }

    /**
     * 公司代付
     * @param Trade $trade
     * @param $companyId
     * @param $userPersonnelId
     * @param bool $arrears
     * @return array
     */
    public function proxyCompanyPay(Trade $trade, $companyId, $userPersonnelId, bool $arrears = false): array
    {
        $payMethod = static::PAYMETHOD;
        $money = $trade->payMoney;

        $memberType = 'company';
        $memberId = $companyId;
        $tradeId = $trade->id;
        $tradeType = $trade->tradeType;
        $describe = $trade->describe;

        $proxyUserPersonnelId = $userPersonnelId;
        $proxyMemberId = $trade->memberId;

        // 余额支付
        $direction = true;

        $success = $this->accountHandle($direction, $memberType, $memberId, $payMethod, $money, $arrears, $memberBeforeMoney, $systemBeforeMoney, $errorCode);

        if (!$success) {
            $this->payFailedHandle($direction, $memberId, $payMethod, $money, $memberBeforeMoney, $systemBeforeMoney, $errorCode);
            return [];
        }

        // 插入账单
        $bill = $this->getAccountBillService()->insertPay($memberType, $memberId, $memberBeforeMoney, $systemBeforeMoney, $payMethod, $money,
            $tradeType, $describe, $proxyUserPersonnelId, $proxyMemberId, $tradeId, null);

        // 交易账单ID作为三方支付单号来保存
        if ($trade->payStatus != Trade::UNPAID) {
            if ($trade->payStatus == Trade::PAID) {
                throw new ValidateException('该订单已支付');
            }
            if ($trade->payStatus == Trade::CANCELLED) {
                throw new ValidateException('该订单已取消');
            }
            throw new ValidateException('该订单已支付');
        }

        $trade->payMemberType = $memberType;
        $trade->payMemberId = $memberId;

        $trade->tradeNid = $bill->id;
        $trade->payStatus = Trade::PAID;
        $trade->payTime = time();
        $trade->save();

        // 触发支付成功事件
        event('PaySuccess', $trade);

        return [
            'needPay' => '102',
        ];
    }

    // **************** 退款 ****************/

    /**
     * 发起退款
     * @param Trade $trade
     * @return bool
     */
    public function refund(Trade $trade): bool
    {
        // 只有未开始和已失败可以发起退款
        if (!in_array($trade->refundResult, [Trade::UNSTART, Trade::FAIL])) {
            return false;
        }

        // 进入退款中状态
        $trade->refundResult = Trade::STARTED;
        if (is_blank($trade->refundNo)) {
            $trade->refundNo = 'REFUND' . $trade->orderNo;
        }
        $trade->save();

        $payMethod = static::PAYMETHOD;

        $memberType = $trade->payMemberType;
        $memberId = $trade->payMemberId;

        $money = $trade->payMoney;
        $tradeType = $trade->tradeType;
        $describe = $trade->describe;
        $tradeId = $trade->id;

        // 退款
        $success = $this->accountHandle(false, $memberType, $memberId, $payMethod, $money, false, $memberBeforeMoney, $systemBeforeMoney, $errorCode);

        if ($success) {
            // 退款成功

            // 查询该订单的支付账单
            $sourceId = AccountBill::where('tradeId', $tradeId)
                ->where('type', 'pay')
                ->where('billType', $tradeType)
                ->value('id');

            // 插入退款账单
            $bill = $this->getAccountBillService()->insertRefund($memberType, $memberId, $memberBeforeMoney, $systemBeforeMoney, $payMethod, $money, $tradeType, $describe, $tradeId, null, $sourceId);

            // 设置订单为退款成功
            $trade->refundNid = $bill->id;
            $trade->refundStatus = Trade::REFUNDED;
            $trade->refundResult = Trade::SUCCESS;
            $trade->refundTime = time();
            $trade->save();

            return true;
        } else {
            // 退款失败

            // 设置退款结果为失败
            $trade->refundResult = Trade::FAIL;
            $trade->refundTime = time();
            $trade->save();

            Log::error('余额退款失败 ID=' . $trade->id);

            return false;
        }
    }

    // **************** 转账 ****************/

    /**
     * 转账收入
     * @param Transfer $transfer
     * @return bool
     */
    public function transfer(Transfer $transfer): bool
    {

        $direction = $transfer->direction == 1;
        $payMethod = static::PAYMETHOD;
        $memberType = $transfer->memberType;
        $memberId = $transfer->memberId;
        $money = $transfer->totalMoney;
        $transferId = $transfer->id;
        $transferType = $transfer->transferType;
        $describe = $transfer->describe;

        // 转账中状态保存
        $transfer->transferStatus = '1';
        $transfer->save();

        // 转账
        $success = $this->accountHandle($direction, $memberType, $memberId, $payMethod, $money, true, $memberBeforeMoney, $systemBeforeMoney, $errorCode);

        if ($success) {
            // 转账成功

            // 插入转账账单
            $bill = $this->getAccountBillService()->insertTransfer($direction, $memberType, $memberId, $memberBeforeMoney, $systemBeforeMoney, $payMethod, $money, $transferType, $describe, $transferId);

            // 设置转账订单为转账成功
            $transfer->tradeNid = $bill->id;
            $transfer->transferStatus = '2';
            $transfer->transferTime = time();
            $transfer->save();

            // 余额转账成功后，触发转账完成事件
            event('TransferComplete', $transfer);

            return true;
        } else {
            // 转账失败

            $transfer->transferStatus = '3';
            $transfer->transferTime = time();
            $transfer->save();

            // 余额转账失败后，触发转账完成事件
            event('TransferComplete', $transfer);

            return false;
        }

    }

    /**
     * 收款账户是否可用
     * @param $memberId
     * @param string $memberType
     * @return bool
     */
    public function isEnableTransfer($memberId, string $memberType = 'member'): bool
    {
        $payMethod = static::PAYMETHOD;

        if (StringUtil::equals($memberType, 'member')) {
            return (bool)Account::where('memberId', $memberId)->where('payMethod', $payMethod)->find();
        } elseif (StringUtil::equals($memberType, 'company')) {
            return (bool)AccountUser::where('id', $memberId)->where('payMethod', $payMethod)->find();
        }
        return false;
    }

    // **************** 反向转账 ****************/

    private function getAccountBillService(): AccountBillService
    {
        return app(AccountBillService::class);
    }

    /**
     * 处理转账
     * @param bool $direction
     * @param $memberType
     * @param $memberId
     * @param $payMethod
     * @param $money
     * @param bool $arrears
     * @param $memberBeforeMoney
     * @param $systemBeforeMoney
     * @param $errorCode
     * @return bool
     */
    private function accountHandle(bool $direction, $memberType, $memberId, $payMethod, $money, $arrears, &$memberBeforeMoney, &$systemBeforeMoney, &$errorCode): bool
    {

        $success = false;
        $errorCode = static::OK;

        $signature = $memberType . ':' . $memberId . ':' . $payMethod;
        $this->synchronizer->synchronized($signature, function () use ($direction, $memberType, $memberId, $payMethod, $money, $arrears, &$memberBeforeMoney, &$systemBeforeMoney, &$success, &$errorCode) {

            // 用户|公司 账户
            $account = null;
            if (StringUtil::equals($memberType, 'member')) {
                $account = Account::where('memberId', $memberId)->where('payMethod', $payMethod)->find();
            } elseif (StringUtil::equals($memberType, 'company')) {
                $account = AccountUser::where('id', $memberId)->where('payMethod', $payMethod)->find();
            }

            if (!$account) {
                $errorCode = static::ERROR_CODE_MEMBER_ACCOUNT_NOT_FOUND;
                return;
            }

            // 系统账户
            $system = AccountSystem::where('id', 1)->where('payMethod', $payMethod)->find();

            if (!$system) {
                $errorCode = static::ERROR_CODE_SYSTEM_ACCOUNT_NOT_FOUND;
                return;
            }

            $memberBeforeMoney = $account->balance;
            $systemBeforeMoney = $system->balance;

            if ($direction) {
                // 用户|公司 给钱

                if ($memberBeforeMoney < $money && !$arrears) {
                    $errorCode = static::ERROR_CODE_MEMBER_ACCOUNT_BALANCE_NOT_ENOUGH;
                    return;
                }

                // 用户|公司 账户 减钱
                $account->balance = $account->balance - $money;
                $account->save();

                // 系统账户 加钱
                $system->balance = $system->balance + $money;
                $system->save();
            } else {
                // 平台给钱

                // 用户|公司 账户 加钱
                $account->balance = $account->balance + $money;
                $account->save();

                // 系统账户 减钱
                $system->balance = $system->balance - $money;
                $system->save();
            }

            $success = true;
        });

        return $success;
    }

    // ***************** 下面是本地支付才有的 *****************

    /**
     * 获取余额
     * @param $memberId
     * @return mixed
     */
    public function getBalance($memberId)
    {
        $payMethod = static::PAYMETHOD;
        return Account::where('memberId', $memberId)->where('payMethod', $payMethod)->value('balance', null);
    }
}
